package com.huijie.app.sys.aspect;

import com.alibaba.fastjson.JSON;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;
import java.util.Objects;

/**
 * @ClassName SystemLogAspect
 * @Description Spring AOP实现全局日志切点管理
 * @Author Alex
 * @Date 2018/10/12 14:08
 * @Version 1.0
 */
@Aspect
@Component
public class SystemLogAspect {

    private Logger logger = LogManager.getLogger(SystemLogAspect.class);

    private static final ThreadLocal<Date> BEGINTIMETHREADLOCAL = new NamedThreadLocal<>("ThreadLocal beginTime");
    private static final ThreadLocal<Date> THREAD_LOCAL_BEGIN_TIME = new NamedThreadLocal<>("ThreadLocal beginTime");

    @Autowired(required=false)

    private HttpServletRequest request;
    @Autowired()
    private PointcutSaveLog pointcutSaveLog;

    /**
     * controller层切点-数据库记录日志，注解方式
     */
    @Pointcut(value = "@annotation(com.huijie.app.sys.aspect.ControllerParamRecord)")
    public void controllerParamRecordAspect() {
        logger.info("========controllerParamRecordAspect===========");
    }

    /**
     * controller层切点-打印日志文件,注解方式
     */
    @Pointcut(value = "@annotation(com.huijie.app.sys.aspect.ControllerParamLog)" /*+
            " && !@annotation(org.springframework.web.bind.annotation.GetMapping)"*/)
    public void controllerParamLogAspect() {
        logger.info("========controllerParamLogAspect===========");
    }

    /**
     * Service层切点,注解方式
     */
    @Pointcut("@annotation(com.huijie.app.sys.aspect.SystemServiceLog)")
    public void serviceAspect() {
        logger.info("========ServiceAspect===========");
    }

    /**
     * 前置通知 (在方法执行之前返回)用于拦截Controller层记录用户的操作的开始时间
     * @param joinPoint 切点
     * @throws InterruptedException
     */
    @Before("controllerParamRecordAspect()")
    public void doBefore(JoinPoint joinPoint) throws InterruptedException{
        //线程绑定变量（该数据只有当前请求的线程可见）
        Date beginTime=new Date();
        BEGINTIMETHREADLOCAL.set(beginTime);
        THREAD_LOCAL_BEGIN_TIME.set(beginTime);
    }

    /**
     * 后置通知(在方法执行之后返回) 用于拦截Controller层操作
     * @param joinPoint 切点
     */
    @After("controllerParamRecordAspect()")
    public void after(JoinPoint joinPoint){
        String logType = "info";
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        pointcutSaveLog.controllerParamSaveLog(request,BEGINTIMETHREADLOCAL,joinPoint,logType,null);
        BEGINTIMETHREADLOCAL.remove();
        THREAD_LOCAL_BEGIN_TIME.remove();
    }

    @Around(value = "controllerParamLogAspect()", argNames = "pjp")
    private ResponseEntity<Object> doAround(ProceedingJoinPoint pjp) throws Exception {
        try {
            String url = request.getRequestURL().toString();
            String method = request.getMethod();
            String uri = request.getRequestURI();
            Object[] args = pjp.getArgs();
            String params = "";
            //获取请求参数集合并进行遍历拼接
            if(args.length>0){
                if("POST".equals(method)){
                    Object object = args[0];
                    Map map = PointcutSaveLog.getKeyAndValue(object);
                    params = JSON.toJSONString(map);
                }else if("GET".equals(method)){
                    params = request.getQueryString();
                }
            }

            logger.info("请求开始===地址:"+url);
            logger.info("请求开始===真实地址:"+uri);
            logger.info("请求开始===类型:"+method);
            logger.info("请求开始===参数:"+params);

            // result的值就是被拦截方法的返回值
            Object result = pjp.proceed();
            return (ResponseEntity<Object>) result;
        } catch (Throwable throwable) {
            logger.info("请求结束===返回值:" + JSON.toJSON(throwable));
            throw new Exception(throwable);
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Service层注解
     * @param joinPoint 切点
     * @return 方法描述
     * @throws Exception
     */
    public static String getServiceMethodDescription(JoinPoint joinPoint) throws Exception{
        //获取目标类名
        String targetName = joinPoint.getTarget().getClass().getName();
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        //获取相关参数
        Object[] arguments = joinPoint.getArgs();
        //生成类对象
        Class targetClass = Class.forName(targetName);
        //获取该类中的方法
        Method[] methods = targetClass.getMethods();

        String description = "";

        for(Method method : methods) {
            if(!method.getName().equals(methodName)) {
                continue;
            }
            Class[] clazzs = method.getParameterTypes();
            //比较方法中参数个数与从切点中获取的参数个数是否相同，原因是方法可以重载哦
            if(clazzs.length != arguments.length) {
                continue;
            }
            description = method.getAnnotation(SystemServiceLog.class).description();
        }
        return description;
    }

    private String getMethodInfo(JoinPoint point) {
        String className = point.getSignature().getDeclaringType().getSimpleName();
        String methodName = point.getSignature().getName();
        String[] parameterNames = ((MethodSignature) point.getSignature()).getParameterNames();
        StringBuilder sb = null;
        if (Objects.nonNull(parameterNames)) {
            sb = new StringBuilder();
            for (int i = 0; i < parameterNames.length; i++) {
                String value = point.getArgs()[i] != null ? point.getArgs()[i].toString() : "null";
                sb.append(parameterNames[i] + ":" + value + "; ");
            }
        }
        sb = sb == null ? new StringBuilder() : sb;
        String info = String.format("class:%s | method:%s | args:%s", className, methodName, sb.toString());
        return info;
    }

}
